package de.ikv.medini.qvt.example;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;

import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.impl.EPackageImpl;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceFactoryImpl;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceImpl;

import uk.ac.kent.cs.kmf.util.ILog;
import uk.ac.kent.cs.kmf.util.OutputStreamLog;
import de.ikv.emf.qvt.EMFQvtProcessorImpl;
import de.ikv.medini.qvt.QVTProcessorConsts;

public class QVTLauncher {
	private ILog logger;

	private EMFQvtProcessorImpl processorImpl;

	protected ResourceSet resourceSet;

	public String targetMM = "./model/SimpleRDBMS.ecore";
	public String sourceMM = "./model/SimpleUML.ecore";
	public String sourceModel = "./model/SimpleUML.xmi";
	public String targetModel = "./model/SimpleRDBMS.xmi";
	public String QVTRule = "./qvt/uml2rdbms.qvt";
	public String transformation = "uml2rdbms"; // TODO: adjust  
	public String direction = "rdbms"; // TODO: adjust
	
	public QVTLauncher(OutputStream outputStream) {
		this.logger = new OutputStreamLog(outputStream);
		this.processorImpl = new EMFQvtProcessorImpl(this.logger);
		this.processorImpl.setProperty(QVTProcessorConsts.PROP_DEBUG, "true");
	}
	
	public void init(Collection<EPackage> ePackages) {
		Iterator<EPackage> iterator = ePackages.iterator();
		while (iterator.hasNext()) {
			this.processorImpl.addMetaModel(iterator.next());
		}
	}
	
	private void clean() {
		this.processorImpl.setModels(Collections.EMPTY_LIST);
	}
	
	public void preExecution(Collection<?> modelResources, URI workingDirectory) {
		this.processorImpl.setWorkingLocation(workingDirectory);
		this.processorImpl.setModels(modelResources);
	}
	
	public void transform(Reader qvtRuleSet, String transformation, String direction) throws Throwable {
		this.processorImpl.evaluateQVT(qvtRuleSet, transformation, true, direction, new Object[0], null, this.logger);
		this.clean();
	}
	
	public Resource getResource(String xmlFileName) {
		URI uri = URI.createFileURI(xmlFileName);
		Resource resource = null;
		try {
			resource = this.resourceSet.getResource(uri, true);
		} catch (Throwable fileNotFoundException) {
			resource = this.resourceSet.createResource(uri);
		}
		return resource;
	}

	public void launch() {

		// initialize resource set of models
		this.resourceSet = new ResourceSetImpl();
		this.resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
		    Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl());
		this.resourceSet = new ResourceSetImpl();  
		this.resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(  

		   Resource.Factory.Registry.DEFAULT_EXTENSION,  
		   new ResourceFactoryImpl()  
		   {  
			   public Resource createResource(URI uri)  

			   {  
				   return new XMIResourceImpl(uri)  
				   {
					   protected boolean useUUIDs() { return true; }  
				   };  

			   }  
		  });
		// TODO: collect all necessary packages from the metamodel(s)
		Collection<EPackage> metaPackages = new ArrayList<EPackage>();
		this.collectMetaModels(metaPackages);

		// make these packages known to the QVT engine
		this.init(metaPackages);

		// load the example model files - TODO: adjust the filename!
		//testcase ShapeLanguage
//		Resource resource1 = this.getResource("./model/source.xmi");
//		Resource resource2 = this.getResource("./model/target.xmi");
		//testcase UMLtoRDBMS
		
		Resource resource1 = this.getResource(sourceModel);
		
		Resource resource2 = this.getResource(targetModel);

		// Collect the models, which should participate in the transformation.
		// You can provide a list of models for each direction.
		// The models must be added in the same order as defined in your transformation!
		Collection<Collection<Resource>> modelResources = new ArrayList<Collection<Resource>>();
		Collection<Resource> firstSetOfModels = new ArrayList<Resource>();
		Collection<Resource> secondSetOfModels = new ArrayList<Resource>();
		modelResources.add(firstSetOfModels);
		modelResources.add(secondSetOfModels);
		firstSetOfModels.add(resource1);
		secondSetOfModels.add(resource2);

		// tell the QVT engine a directory to work in - e.g. to store the trace (meta)models
		URI directory = URI.createFileURI("./traces");
		this.preExecution(modelResources, directory);

		// load the QVT relations
		FileReader qvtRuleSet = null;
		try {
			//testcase ShapeLanguage
//			qvtRuleSet = new FileReader("./qvt/yet-another.qvt");
			
			//testcase UMLtoRDBMS
			
			qvtRuleSet = new FileReader(QVTRule); // TODO: adjust the filename!
		} catch (FileNotFoundException fileNotFoundException) {
			fileNotFoundException.printStackTrace();
			return;
		}
		// tell the QVT engine, which transformation to execute - there might be more than one in
		// the QVT file!
		
		//testcae ShapeLanguage
//		String transformation = "yetAnotherTrafo"; // TODO: adjust  
		// give the direction of the transformation (according to the transformation definition)
//		String direction = "thisWay"; // TODO: adjust
		
		//testcase UMLtoRDBMS
		

		// just do it ;-)
		try {
			this.transform(qvtRuleSet, transformation, direction);
		} catch (Throwable throwable) {
			throwable.printStackTrace();
		}

		// Note: the engine does not save the model resources, which were participating in the
		// transformation.
		// You have to take care on this.
		try {
			resource2.save(Collections.EMPTY_MAP);
		} catch (IOException exception) {
			exception.printStackTrace();
		}
	}
	

	
	protected void collectMetaModels(Collection<EPackage> metaPackages) {

		// the resource set for the metamodels
		ResourceSetImpl mmResourceSet = new ResourceSetImpl();
		mmResourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
		    Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl());

		// TODO if we load the metamodels NOT into "resourceSet", then the traces cannot be loaded!
		
		EPackageImpl p1 = UsingMediniQVTfromApplicationsReflective.buildMetaModelFromEcoreFile(
		    sourceMM, this.resourceSet);

		// make the metamodels known to the resourceset of the models
		this.resourceSet.getPackageRegistry().put(p1.getNsURI(), p1);
		// otherwise we would get
		// "(Relation 'RootBlock2RootBlock' initially has 0 tuple(s) to evaluate)" ..

		metaPackages.add(p1);
		
		EPackageImpl p2 = UsingMediniQVTfromApplicationsReflective.buildMetaModelFromEcoreFile(
		    targetMM, this.resourceSet);
		this.resourceSet.getPackageRegistry().put(p2.getNsURI(), p2);

		metaPackages.add(p2);
	}
	
	static protected EPackageImpl buildMetaModelFromEcoreFile(String path, ResourceSet rset) {
		URI simpleMMFileUri;
		try {
			simpleMMFileUri = URI.createFileURI(path); // e.g. "c:\.."
		} catch (Exception e) {
			simpleMMFileUri = URI.createURI(path); // e.g. file:/c:/.. or
			// platform:/resource/MyProjectName/..
		}
		Resource simpleMMResource = rset.getResource(simpleMMFileUri, true);
		return (EPackageImpl) simpleMMResource.getContents().get(0);
	}

}
